//! String helpers that resemble Lodash string utilities.
//! 字符串类 Lodash 工具函数，覆盖大小写转换、分词、截断等场景。

use unicode_segmentation::UnicodeSegmentation;

/// Splits an input into lowercase word tokens, like `_.words`.
/// 类似 `_.words`，将输入拆分成小写的词元集合。
pub fn words(input: &str) -> Vec<String> {
    if input.trim().is_empty() {
        return Vec::new();
    }

    let mut tokens = Vec::new();
    for token in input.split(is_separator) {
        if token.is_empty() {
            continue;
        }
        split_token(token, &mut tokens);
    }

    tokens
}

/// Converts input to `camelCase`.
/// 转换成 `camelCase` 小驼峰格式。
pub fn camel_case(input: &str) -> String {
    let mut iter = words(input).into_iter();
    let first = match iter.next() {
        Some(word) => word,
        None => return String::new(),
    };

    let mut out = first;
    for word in iter {
        out.push_str(&capitalize(&word));
    }
    out
}

/// Converts input to `kebab-case`.
/// 转换成 `kebab-case` 短横线格式。
pub fn kebab_case(input: &str) -> String {
    words(input).join("-")
}

/// Converts input to `snake_case`.
/// 转换成 `snake_case` 下划线格式。
pub fn snake_case(input: &str) -> String {
    words(input).join("_")
}

/// Converts input to start case (space-delimited capitalized words).
/// 转换成首字母大写、以空格分隔的单词。
pub fn start_case(input: &str) -> String {
    words(input).into_iter().map(|word| capitalize(&word)).collect::<Vec<_>>().join(" ")
}

/// Capitalizes the first character and lowercases the rest, akin to `_.capitalize`.
/// 类似 `_.capitalize`，首字母大写，其余转小写。
pub fn capitalize(word: &str) -> String {
    let mut chars = word.chars();
    match chars.next() {
        Some(first) => {
            let mut result = first.to_uppercase().collect::<String>();
            result.push_str(&chars.as_str().to_lowercase());
            result
        },
        None => String::new(),
    }
}

/// Truncates a string to `max_length` graphemes, appending `...` when trimmed.
/// 按 Unicode 字符簇安全截断，并在超长时追加 `...`。
pub fn truncate(
    input: &str,
    max_length: usize,
) -> String {
    if max_length == 0 {
        return String::new();
    }

    let graphemes: Vec<&str> = UnicodeSegmentation::graphemes(input, true).collect();
    if graphemes.len() <= max_length {
        return input.to_string();
    }

    if max_length <= 3 {
        return ".".repeat(max_length);
    }

    let visible = graphemes[..max_length - 3].concat();
    format!("{visible}...")
}

fn split_token(
    token: &str,
    output: &mut Vec<String>,
) {
    let chars: Vec<char> = token.chars().collect();
    if chars.is_empty() {
        return;
    }

    let mut start = 0;
    for idx in 1..chars.len() {
        let prev = chars[idx - 1];
        let curr = chars[idx];
        let next = chars.get(idx + 1).copied();

        if should_break(prev, curr, next) {
            push_segment(&chars, start, idx, output);
            start = idx;
        }
    }

    push_segment(&chars, start, chars.len(), output);
}

fn push_segment(
    chars: &[char],
    start: usize,
    end: usize,
    output: &mut Vec<String>,
) {
    if start >= end {
        return;
    }
    let segment: String = chars[start..end].iter().collect();
    output.push(segment.to_lowercase());
}

fn should_break(
    prev: char,
    curr: char,
    next: Option<char>,
) -> bool {
    let prev_is_digit = prev.is_numeric();
    let curr_is_digit = curr.is_numeric();

    if prev_is_digit && !curr_is_digit {
        return true;
    }
    if !prev_is_digit && curr_is_digit {
        return true;
    }
    if prev.is_lowercase() && curr.is_uppercase() {
        return true;
    }
    if prev.is_uppercase() && curr.is_uppercase() {
        if let Some(next) = next {
            if next.is_lowercase() {
                return true;
            }
        }
    }

    false
}

fn is_separator(ch: char) -> bool {
    !ch.is_alphanumeric()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn word_detection_handles_mixed_inputs() {
        assert_eq!(words("PriceUtils"), vec!["price", "utils"]);
        assert_eq!(words("HTTPServer42App"), vec!["http", "server", "42", "app"]);
        assert_eq!(words("foo_bar-baz"), vec!["foo", "bar", "baz"]);
    }

    #[test]
    fn case_conversions_match_expectations() {
        let input = "Hello world-example";
        assert_eq!(camel_case(input), "helloWorldExample");
        assert_eq!(kebab_case(input), "hello-world-example");
        assert_eq!(snake_case(input), "hello_world_example");
        assert_eq!(start_case(input), "Hello World Example");
    }

    #[test]
    fn truncate_respects_grapheme_boundaries() {
        let text = "价格超长文本";
        assert_eq!(truncate(text, 2), "..");
        assert_eq!(truncate(text, 4), "价...");
        assert_eq!(truncate(text, 64), text);
    }
}
